package com.iacrqq.sedaf.demo.sHttpServer.event.handler;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URLDecoder;
import java.util.Locale;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseFactory;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpServerConnection;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.ProtocolVersion;
import org.apache.http.entity.ContentProducer;
import org.apache.http.entity.EntityTemplate;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.params.DefaultedHttpParams;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpExpectationVerifier;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.HttpRequestHandlerResolver;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.apache.http.util.EntityUtils;

import com.iacrqq.sedaf.config.Configuration;
import com.iacrqq.sedaf.demo.sHttpServer.event.HttpEvent;
import com.iacrqq.sedaf.demo.sHttpServer.event.HttpEventStateEnum;
import com.iacrqq.sedaf.event.Event;
import com.iacrqq.sedaf.event.EventDispatcher;
import com.iacrqq.sedaf.event.exception.EventHandlerException;

public class ProcessEventHandler extends AbstractEventHandler
{
	private static final Log logger = LogFactory.getLog(ProcessEventHandler.class);

	private volatile HttpResponseFactory responseFactory = null;
	private volatile HttpRequestHandlerRegistry reqistry = null;
	private volatile HttpExpectationVerifier expectationVerifier = null;
	private volatile HttpProcessor processor = null;
	private volatile HttpRequestHandlerResolver handlerResolver = null;

	@Override
	public void init(Configuration config) throws Exception
	{
		this.responseFactory = new DefaultHttpResponseFactory();
		this.reqistry = new HttpRequestHandlerRegistry();
		reqistry.register("*", new HttpFileHandler(config.getString("process.docroot")));
		// Set up the HTTP protocol processor
		processor = new ImmutableHttpProcessor(new HttpResponseInterceptor[] { new ResponseDate(),
				new ResponseServer(), new ResponseContent(), new ResponseConnControl() });
		this.handlerResolver = this.reqistry;
		System.out.println("ProcessEventHandler initialized.");
	}

	@Override
	public void destroy() throws Exception
	{
		System.out.println("ProcessEventHandler destroied.");
	}

	@Override
	public Event handleEvent(Event event, EventDispatcher eventDispatcher) throws EventHandlerException
	{
		HttpEvent httpEvent = (HttpEvent) event;
		HttpResponse response = null;
		HttpRequest request = httpEvent.getHttpRequest();
		HttpServerConnection httpServerConnection = httpEvent.getHttpServerConnection();

		if (httpEvent.getHttpException() != null)
		{
			return httpEvent;
		}

		ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
		if (!ver.lessEquals(HttpVersion.HTTP_1_1))
		{
			// Downgrade protocol version if greater than HTTP/1.1
			ver = HttpVersion.HTTP_1_1;
		}

		HttpContext context = httpEvent.getHttpContext();
		try
		{
			if (request instanceof HttpEntityEnclosingRequest)
			{

				if (((HttpEntityEnclosingRequest) request).expectContinue())
				{
					response = this.responseFactory.newHttpResponse(ver, HttpStatus.SC_CONTINUE, context);
					response.setParams(new DefaultedHttpParams(response.getParams(), this.params));

					if (this.expectationVerifier != null)
					{
						try
						{
							this.expectationVerifier.verify(request, response, context);
						}
						catch (HttpException ex)
						{
							response = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_0,
									HttpStatus.SC_INTERNAL_SERVER_ERROR, context);
							response.setParams(new DefaultedHttpParams(response.getParams(), this.params));
							_handle_exception_(ex, response);
						}
					}
					if (response.getStatusLine().getStatusCode() < 200)
					{
						// Send 1xx response indicating the server expections
						// have been met
						httpServerConnection.sendResponseHeader(response);
						httpServerConnection.flush();
						response = null;
						httpServerConnection.receiveRequestEntity((HttpEntityEnclosingRequest) request);
					}
				}
				else
				{
					httpServerConnection.receiveRequestEntity((HttpEntityEnclosingRequest) request);
				}
			}

			if (response == null)
			{
				response = this.responseFactory.newHttpResponse(ver, HttpStatus.SC_OK, context);
				response.setParams(new DefaultedHttpParams(response.getParams(), this.params));

				context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
				context.setAttribute(ExecutionContext.HTTP_RESPONSE, response);

				this.processor.process(request, context);
				_do_service_(request, response, context);
			}

			// Make sure the request content is fully consumed
			if (request instanceof HttpEntityEnclosingRequest)
			{
				HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
				EntityUtils.consume(entity);
			}

			httpEvent.setHttpResponse(response);

			httpEvent.setState(HttpEventStateEnum.HTTP_EVENT_STATE_PROCESSED.value());
			return httpEvent;
		}
		catch (HttpException e)
		{
			httpEvent.setHttpException(e);
			return httpEvent;
		}
		catch (IOException e)
		{
			logger.error(e);
			throw new EventHandlerException(e);
		}
	}

	@Override
	public Event[] handleEvents(Event[] events, EventDispatcher eventDispatcher) throws EventHandlerException
	{
		// System.out.println(new
		// StringBuilder("ProcessEventHandler handle ").append(events.length).append(" events:").append(events).toString());

		return events;
	}

	protected void _do_service_(final HttpRequest request, final HttpResponse response, final HttpContext context)
			throws HttpException, IOException
	{
		HttpRequestHandler handler = null;
		if (this.handlerResolver != null)
		{
			String requestURI = request.getRequestLine().getUri();
			handler = this.handlerResolver.lookup(requestURI);
		}
		if (handler != null)
		{
			handler.handle(request, response, context);
		}
		else
		{
			response.setStatusCode(HttpStatus.SC_NOT_IMPLEMENTED);
		}
	}

	static class HttpFileHandler implements HttpRequestHandler
	{

		private final String docRoot;

		public HttpFileHandler(final String docRoot)
		{
			super();
			this.docRoot = docRoot;
		}

		public void handle(final HttpRequest request, final HttpResponse response, final HttpContext context)
				throws HttpException, IOException
		{

			String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH);
			if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST"))
			{
				throw new MethodNotSupportedException(method + " method not supported");
			}
			String target = request.getRequestLine().getUri();

			if (request instanceof HttpEntityEnclosingRequest)
			{
				HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
				byte[] entityContent = EntityUtils.toByteArray(entity);
				System.out.println("Incoming entity content (bytes): " + entityContent.length);
			}

			final File file = new File(this.docRoot, URLDecoder.decode(target));
			if (!file.exists())
			{

				response.setStatusCode(HttpStatus.SC_NOT_FOUND);
				EntityTemplate body = new EntityTemplate(new ContentProducer()
				{

					public void writeTo(final OutputStream outstream) throws IOException
					{
						OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8");
						writer.write("<html><body><h1>");
						writer.write("File ");
						writer.write(file.getPath());
						writer.write(" not found");
						writer.write("</h1></body></html>");
						writer.flush();
					}

				});
				body.setContentType("text/html; charset=UTF-8");
				response.setEntity(body);
				//System.out.println("File " + file.getPath() + " not found");

			}
			else if (!file.canRead() || file.isDirectory())
			{

				response.setStatusCode(HttpStatus.SC_FORBIDDEN);
				EntityTemplate body = new EntityTemplate(new ContentProducer()
				{

					public void writeTo(final OutputStream outstream) throws IOException
					{
						OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8");
						writer.write("<html><body><h1>");
						writer.write("Access denied");
						writer.write("</h1></body></html>");
						writer.flush();
					}

				});
				body.setContentType("text/html; charset=UTF-8");
				response.setEntity(body);
				//System.out.println("Cannot read file " + file.getPath());

			}
			else
			{

				response.setStatusCode(HttpStatus.SC_OK);
				FileEntity body = new FileEntity(file, "text/html");
				response.setEntity(body);
				//System.out.println("Serving file " + file.getPath());

			}
		}
	}

}
